package com.ohosadvance.topsnackbar;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;

import java.lang.ref.WeakReference;

/**
 * description :
 *
 * @since : 2021/7/1
 */
class SnackbarManager {
    private static final int MSG_TIMEOUT = 0;
    private static final int SHORT_DURATION_MS = 1500;
    private static final int LONG_DURATION_MS = 2750;

    private final Object mLock;
    private final MyHandler mEventHandler;
    private SnackbarRecord mCurrentSnackbar;
    private SnackbarRecord mNextSnackbar;

    static SnackbarManager getInstance() {
        return ManagerHolder.INSTANCE;
    }

    private SnackbarManager() {
        mLock = new Object();
        mEventHandler = new MyHandler(EventRunner.getMainEventRunner());
    }

    interface Callback {
        void show();

        void dismiss(int event);
    }

    void show(int duration, Callback callback) {
        synchronized (mLock) {
            if (isCurrentSnackbar(callback)) {
                // Means that the callback is already in the queue. We'll just update the duration
                mCurrentSnackbar.duration = duration;
                // If this is the TSnackbar currently being shown, call re-schedule it's
                // timeout
                mEventHandler.removeAllEvent();
                scheduleTimeoutLocked(mCurrentSnackbar);
                return;
            } else if (isNextSnackbar(callback)) {
                // We'll just update the duration
                mNextSnackbar.duration = duration;
            } else {
                // Else, we need to create a new record and queue it
                mNextSnackbar = new SnackbarRecord(duration, callback);
            }

            if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,
                    TSnackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {
                // If we currently have a TSnackbar, try and cancel it and wait in line
                return;
            } else {
                // Clear out the current snackbar
                mCurrentSnackbar = null;
                // Otherwise, just show it now
                showNextSnackbarLocked();
            }
        }
    }

    void dismiss(Callback callback, int event) {
        synchronized (mLock) {
            if (isCurrentSnackbar(callback)) {
                cancelSnackbarLocked(mCurrentSnackbar, event);
            } else {
                if (isNextSnackbar(callback)) {
                    cancelSnackbarLocked(mNextSnackbar, event);
                }
            }
        }
    }

    /**
     * Should be called when a TSnackbar is no longer displayed. This is after any exit
     * animation has finished.
     * @param callback SnackbarManager.Callback
     */
    void onDismissed(Callback callback) {
        synchronized (mLock) {
            if (isCurrentSnackbar(callback)) {
                // If the callback is from a TSnackbar currently show, remove it and show a new one
                mCurrentSnackbar = null;
                if (mNextSnackbar != null) {
                    showNextSnackbarLocked();
                }
            }
        }
    }

    /**
     * Should be called when a TSnackbar is being shown. This is after any entrance animation has
     * finished.
     * @param callback SnackbarManager.Callback
     */
    void onShown(Callback callback) {
        synchronized (mLock) {
            if (isCurrentSnackbar(callback)) {
                scheduleTimeoutLocked(mCurrentSnackbar);
            }
        }
    }

    void cancelTimeout(Callback callback) {
        synchronized (mLock) {
            if (isCurrentSnackbar(callback)) {
                mEventHandler.removeAllEvent();
            }
        }
    }

    void restoreTimeout(Callback callback) {
        synchronized (mLock) {
            if (isCurrentSnackbar(callback)) {
                scheduleTimeoutLocked(mCurrentSnackbar);
            }
        }
    }

    boolean isCurrent(Callback callback) {
        synchronized (mLock) {
            return isCurrentSnackbar(callback);
        }
    }

    boolean isCurrentOrNext(Callback callback) {
        synchronized (mLock) {
            return isCurrentSnackbar(callback) || isNextSnackbar(callback);
        }
    }

    private static class SnackbarRecord {
        private final WeakReference<Callback> callback;
        private int duration;

        SnackbarRecord(int duration, Callback callback) {
            this.callback = new WeakReference<>(callback);
            this.duration = duration;
        }

        boolean isSnackbar(Callback callback) {
            return callback != null && this.callback.get() == callback;
        }
    }

    private void showNextSnackbarLocked() {
        if (mNextSnackbar != null) {
            mCurrentSnackbar = mNextSnackbar;
            mNextSnackbar = null;

            final Callback callback = mCurrentSnackbar.callback.get();
            if (callback != null) {
                callback.show();
            } else {
                // The callback doesn't exist any more, clear out the TSnackbar
                mCurrentSnackbar = null;
            }
        }
    }

    private boolean cancelSnackbarLocked(SnackbarRecord record, int event) {
        final Callback callback = record.callback.get();
        if (callback != null) {
            callback.dismiss(event);
            return true;
        }
        return false;
    }

    private boolean isCurrentSnackbar(Callback callback) {
        return mCurrentSnackbar != null && mCurrentSnackbar.isSnackbar(callback);
    }

    private boolean isNextSnackbar(Callback callback) {
        return mNextSnackbar != null && mNextSnackbar.isSnackbar(callback);
    }

    private void scheduleTimeoutLocked(SnackbarRecord record) {
        if (record.duration == TSnackbar.LENGTH_INDEFINITE) {
            // If we're set to indefinite, we don't want to set a timeout
            return;
        }

        int durationMs = LONG_DURATION_MS;
        if (record.duration > 0) {
            durationMs = record.duration;
        } else {
            if (record.duration == TSnackbar.LENGTH_SHORT) {
                durationMs = SHORT_DURATION_MS;
            }
        }
        mEventHandler.removeAllEvent();
        mEventHandler.sendEvent(InnerEvent.get(MSG_TIMEOUT, record), durationMs);
    }

    private void handleTimeout(SnackbarRecord record) {
        synchronized (mLock) {
            if (mCurrentSnackbar == record || mNextSnackbar == record) {
                cancelSnackbarLocked(record, TSnackbar.Callback.DISMISS_EVENT_TIMEOUT);
            }
        }
    }

    private class MyHandler extends EventHandler {
        private MyHandler(EventRunner runner) {
            super(runner);
        }

        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            switch (event.eventId) {
                case MSG_TIMEOUT:
                    handleTimeout((SnackbarRecord) event.object);
            }
        }
    }

    private static class ManagerHolder {
        private static final SnackbarManager INSTANCE = new SnackbarManager();
    }

}
